/**
 * Gestalt Application Server
 * maintainer: G. John Schutte IV (john@schutte.me)
 * 
 * Launch:
 *  node.exe gestalt.js [options]
 * 
 * http://host:port/landing.ui # HTML-based UI files
 * http://host:port/logic.node  # Node.js Modules
 *      stored: ~/nodes/logic.node.js
 * 
 * Application Organization
 *  app/
 *      settings.json   -- Settings file for Application instance
 *      manifest.json   -- Application package settings
 *      ui/
 *          main.ui   	-- Landing page for User Interface
 *          media/
 *          css/
 *          js/
 *      assets/         -- Templates, data files and protected files used by Node Modules
 *      nodes/          -- Custom Node.js server-side nodes, all server-side nodes return JSON data
 *			main.node.js 	-- Accessed by /main.node 
 * 
 *  .node.js files are passed a "request" object that contains
 *      the following properties:
 *      (Inherited from: url.parse(req.url))
 *		request.
 *          href
 *          protocol
 *          host
 *          auth
 *          hostname
 *          port
 *          pathname
 *          search
 *          path
 *          query
 *          hash
 *      
 *      (Additional, from http.incomingMessage)
 *          method
 *          postdata
 *          
 **/

var url = require("url"),
    qs = require("querystring"),
    path = require("path"),
    fs = require("fs"),
	os = require("os"),
	fml = require("fml").Interpreter;

// Server settings
// Push to external file (TODO)
var settings = {
    host: "localhost",
    port: 1337,
    docroot : path.join(__dirname, "ui"),
    assets: path.join(__dirname, "assets"),
    nodes: path.join(__dirname, "nodes"),
	listDirectories: true,
    landing: "main.ui",
	allowShutdown: true,
	secure: false,
	logging: false,
	debug: false,
	client: "auto"
};

// User defined mime-types
// Push to external file (TODO)
var mimes = {
	".json" : "application/json",
	".ui" : "text/html; charset=utf-8",
	".v8" : "text/html",
	".ico" : "image/x-icon",
	".png" : "image/png",
	".jpg" : "image/jpeg",
	".svg" : "image/svg+xml",
  ".svgs" : "image/svg+xml",
	".gif" : "image/gif",
	".pdf" : "application/pdf",
	".html" : "text/html; charset=utf-8",
	".css" : "text/css",
	".js" : "text/javascript"
};

var launchClient = false,
	debug = false,
	logging = false,
	secure = false,
	reqID = "",
	app = "";

/**
 * 	Command-line Switches and Overrides
 *		node gestalt.node.js [switches]
 **/
// Flags
// --log		Write debug chatter to $assets$/gestalt.log
// --debug		Write debug chatter to console.log (stdout)
// --secure		Run AppServer in HTTPS mode, default is HTTP
// --client		Launch client

if (/--debug/gi.test(process.argv)) debug = true;
if (/--log/gi.test(process.argv)) logging = true;
if (/--secure/gi.test(process.argv)) secure = true;
if (/--client/gi.test(process.argv)) launchClient = false;
if (/--listdir/gi.test(process.argv)) settings.listDirectories = true;

if (/--app/i.test(process.argv)){
	app = process.argv[process.argv.indexOf("--app") + 1];
	try{
		settings = require(path.join(__dirname, app, "settings.json"));
		// Add paths to settings 
		settings.docroot = path.join(__dirname, app, settings.docroot);
		settings.nodes = path.join(__dirname, app, settings.nodes);
		settings.assets = path.join(__dirname, app, settings.assets);
		
	}catch (e){
		console.log("ERROR: Could not load server configuration file: " + path.join(__dirname, process.argv[process.argv.indexOf("--app") + 1], "settings.json") + "\n" + e);
		process.exit(0);
	}
}

// Settings overrides
if (/--port/.test(process.argv)) settings.port = process.argv[process.argv.indexOf("--port") + 1];
if (/--host/.test(process.argv)) settings.host = process.argv[process.argv.indexOf("--host") + 1];

// Create Logfile
if (logging){
	var logfile = fs.createWriteStream(path.join(settings.assets, "gestalt-" + String(Date.now()) + ".log"), { flags: "a" });
}

// Logger function 
function logger(msg, prefix){
	if (!prefix || prefix === "" || prefix === undefined) prefix = "GESTALT";
	if (debug) console.log(msg);
	if (logging){
		logfile.write(prefix + "> " + msg + "\n", "utf-8", function(e){ if (e) console.log(e) });
	};
}

// If we are running in "secure" mode, load certificates.
var certs = {};
if (secure){
	try{
		certs.key = fs.readFileSync(path.join(settings.assets, "gestalt.key"));
		certs.cert = fs.readFileSync(path.join(settings.assets, "gestalt.cert"));
		logger("SSL Key and Certificate loaded.  Running in HTTPS mode.");
	}catch(e){
		secure = false;
		logger("Could not load SSL Key and/or Certificate.  Running in HTTP mode.");
	}
}

// Main Handler 
function GestaltHandler(req, res){
	reqID = Date.now();
	console.log("REQUEST: " + reqID + "; " + req.url + "; " + req.connection.remoteAddress);
	logger("Request ID: " + reqID);
    logger(req.url, reqID);
    var uri = url.parse(req.url).pathname;
    var file = path.join(settings.docroot, uri);
    var node_module = false;
    
    if (path.extname(file) == ".node"){
        logger("Node Module: " + file, reqID);
        file = path.join(settings.nodes, uri + ".js");
        node_module = true;
    };
    
	/**
	 * Client-commands
	 *		Special keywords that provide information from the client, or performs a special action 
	 **/
	// host:port/_shutdown
	if (/_shutdown/i.test(req.url) && settings.allowShutdown){
		logger("Shutdown command received from Client.", reqID);
		serve("application/json", JSON.stringify({ error: false, data: "Gestalt received 'shutdown' command, killing server.", code: 200 }));
		process.exit(0);
	}
	
	// host:port/_status
	if (/_status/i.test(req.url)){
		logger("Server status requested from Client.", reqID);
		var serverStatus = {
			os: os.type() + " (" + os.platform() + ")",
			arch: os.arch(),
			memory: {
				total: os.totalmem(),
				free: os.freemem(),
				used: os.totalmem() - os.freemem()
			},
			cpus: {
				model: os.cpus()[0].model,
				speed: os.cpus()[0].speed,
				cores: os.cpus().length
			}
		};
		
		serve("application/json", JSON.stringify({ error: false, data: serverStatus, code: 200 }, null, "\t"));
		return;
	}
	
	// host:port/_echo
	if (/_echo/i.test(req.url)){
		logger("Echo request from client.", reqID);
		var echodata = url.parse(req.url);
		serve("application/json", JSON.stringify({ error: false, data: echodata, code: 200 }, null, "\t"));
		return;
	}
	
    // If the request points to a directory, point to landing page
    if (!fs.existsSync(file)){
        // File not Found
        res.writeHead(404, { "content-type" : "application/json" });
        res.write(JSON.stringify({ "error" : "true", "msg" : "404: Asset not found." }, null, "\t"));
        res.end();
        return;
    }else{
		// Path exists, check if it is a directory...
        if (fs.statSync(file).isDirectory()){
			// It is a directory, make sure that the Landing page exists...
			if (!fs.existsSync(path.join(file, settings.landing))){
				// No landing page...
				if (settings.listDirectories){
					// listDirectories is true, so provide a directory listing.
					serve("application/json", JSON.stringify({ error: false, data: fs.readdirSync(file), code: 200 }, null, "\t"));
					return;
				}else{
					// listDirectories is false, so provide an error.
					serve("application/json", JSON.stringify({ error: true, data: "Directory listing is disabled.", code: 404 }, null, "\t"));
					return;
				}
			}else{
				// Set destination to the landing page
				logger("Dierctory request, appending landing page.", reqID);
				file += settings.landing;
			}
		}
    }
    
    logger("Asset: " + file, reqID);
    
    // Get any POST data there might be, include it in the request object
    var request = url.parse(req.url, true);
    request.method = req.method;
    request.postdata = false;
	request.server = settings;
    
    logger("Request Type: " + req.method, reqID);
    if (req.method == "post" || req.method == "POST"){
        logger("Receiving POST data...", reqID);
        var tmp = "";
        req.on("data", function(data){
            tmp += data;
        });
        req.on("end", function(){
            logger("...POST data received!", reqID);
            request.postdata = qs.parse(tmp);
            handle();
        });
    }else{
        handle();
    }
    
    function err(errno, msg){
        logger(errno + ": " + msg, "ERROR");
        res.writeHead(404, { "content-type" : "application/json" });
        res.write(JSON.stringify({ error: true, data: msg, code: errno }, null, "\t"));
        res.end();
        return;
    }
    
	function getMimeType(f){
		if (path.extname(f) in mimes){
			logger("Detected Mime-Type: " + mimes[path.extname(f)], reqID);
			return mimes[path.extname(f)];
		}else{
			logger("Guessing Mime-Type: " + path.extname(f), reqID);
			return "auto";
		}
	}
	
    function serve(contentType, data){
        logger("Serving Request...", reqID);
        if (contentType === "auto"){
            res.writeHead(200);
        }else{
            res.writeHead(200, { "content-type" : contentType + "; charset=utf-8" });
        }
		
        if (data !== "" || data !== false) res.write(data);
        res.end();
        logger("...Request served!", reqID);
        return;
    }
    
    function handle(){
        logger("Handling Request...", reqID);
        if (/favicon\.ico/gi.test(req.url)){
			// Ignore favicon requests
			logger("Ignoring FAVICON request.", reqID);
			serve("image/x-icon", false);
		}
		
        if (node_module){
            fs.exists(file, function(ex){
                logger("Node Module exists...", reqID);
                var content = "";
                if (!ex) err(404, "Module '" + file + "' was not found.");
                content = require(file).Handler(request);
                if (content === "" || content === undefined){
                    content = { error: true, data: "Node Module returned invalid result.", code: 505 };
                }
                serve("application/json", JSON.stringify(content, null, "\t"));
            });
        }
        
        fs.exists(file, function(ex){
            logger("Checking if asset exists...", reqID);
            if (!ex){
                logger("...asset does not exist!", reqID);
                err(404, "Asset '" + file + "' does not exist, or cannot be found.");
            }
            
            logger("...asset does exist!", reqID);
			if (path.extname(file) == ".v8"){
				serve(getMimeType(file), fml(file));
			}else{
				fs.readFile(file, "utf8", function(e, data){
					logger("Loading Asset...", reqID);
					if (e){
						err(500, "Internal server error: " + e + "; " + file);
					}
					logger("Serving asset: " + path.basename(file), reqID);
					serve(getMimeType(file), data);
				});
			}
        });
    }
}

if (secure){
	server = require("https");
	logger("Starting Gestalt in Secure mode.");
	try{
		server.createServer(certs, GestaltHandler).listen(settings.port, settings.host);
	}catch (e){
		console.log("ERROR: Failed to start Gestalt Instance. \n" + e);
	}
	
}else{
	server = require("http");
	logger("Starting Gestalt in Standard mode.");
	try{
		server.createServer(GestaltHandler).listen(settings.port, settings.host);
	}catch (e){
		console.log("ERROR: Failed to start Gestalt Instance. \n" + e);
	}
}
console.log("Gestalt Instance: " + settings.host + ":" + settings.port + " started.");